//
// Created by T on 2018/11/26.
//

#include "esp8266.h"
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include "utils.h"
#include "main.h"
#include "pkt.h"
#include "log.h"

#define DBG_ATCMD 1
#if DBG_ATCMD
#define AT_CMD_DBG(fmt, args...) printf(fmt, ## args)
#else
#define AT_CMD_DBG(fmt, args...)
#endif

struct esp8266_uart {
    UART_HandleTypeDef *huart;
    struct fifo *read_fifo;
};

struct esp8266_cfg {
    const char *ssid;
    const char *psk;
    const char *server_host;
    int server_port;
};

struct esp8266_ctx;
struct esp8266_cmd_ctx;
typedef void (*cmd_done_cb_t)(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2);

struct esp8266_cmd_ctx {
    // AT 命令
    const char cmd[512];
    // AT 命令字节数
    size_t cmd_len;
    // 直接写入 ESP8266 串口的数据vector，与 cmd 类似
    const struct iovec *cmdvec;
    // 有效的 cmdvec 数量
    int cmdvcnt;
    int cmdvecidx;

    // 命名超时时间
    uint32_t timeout;
    // 命令启动时间
    uint32_t tickstart;

    // 命令正在发送过程中
    int cmd_writing;

    // 回响（指向 rspbuf）
    char *rsp;
    // 回响数据字节书
    size_t rsp_len;

    // 回响匹配参考，以行作为基准，以 NULL 结束。
    // 例如命令 "AT+CWMODE?"，回响是 "+CWMODE:...\r\n\n\nOK\r\n"，则 wait_rsp_expect_tab 的内容为：
    //     {{"+CWMODE:", "", "OK", NULL}, NULL}
    // 多种情况的回响，例如命令 "AT+CWJAP?"，回响可能有两种情况：
    //     1. "No AP\r\n\r\nOK\r\n"
    //     2. "+CWJAP:...\r\n\r\nOK\r\n"
    //     则 wait_rsp_expect_tab 的内容为：
    //     {{"No AP", "OK", NULL}, {"+CWJAP:", "OK", NULL}, NULL}
    const char *wait_rsp_expect_tab[4][8];
    int tab_idx;
    int expect_idx;
    char rspbuf[1024 + 256];
    char rspbuf_write_idx;

    cmd_done_cb_t cmd_done_cb;
    void *udata;
    uintptr_t udata2;
};

struct esp8266_ctx {
    struct esp8266_uart uart;
    struct esp8266_cfg cfg;
    struct esp8266_cmd_ctx cmd_ctx;
    int AT_check_failure_times;
    unsigned int status;
    int ipd_n; // +IPD,n,xxx 通知中的 n，无效时为 -2；确定了 +IPD, 但还未解析到 n 为 -1。
    uint32_t ipd_tickstart;
    uint32_t ipd_timeout;
    struct iovec iovec;
    net_send_done_cb_t net_send_done_cb;
    void *net_send_done_cb_udata;
#ifdef EN_ESP8266_CMD_BLOCKING
    char buf[2048];
#endif
} ctx;

#define INIT_JOIN_AP_CFG_DONE   0x00001U
#define AP_JOINED               0x00100U
#define GOT_IP                  0x00200U
#define TCP_CONNECTED           0x00400U
#define TCP_DIAL_DONE           0x00800U
#define JUST_REDIAL_TCP         0x01000U
#define IN_CIPSEND              0x10000U
#define IN_CIPSEND_STAGE_1      0x20000U
#define IOVEC_BUSY              0x40000U

char buf[2048];
size_t bufidx;




char *HAL_StatusTypeDef_str(HAL_StatusTypeDef status)
{
    switch (status) {
        case HAL_OK:
            return "OK";
        case HAL_ERROR:
            return "ERROR";
        case HAL_BUSY:
            return "BUSY";
        case HAL_TIMEOUT:
            return "TIMEOUT";
        default:
            return "UNKNOWN_ERR";
    }
}


static void esp8266_on_tcp_connected(struct esp8266_ctx *ctx)
{
    on_tcp_connected();
}




void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (ctx.cmd_ctx.cmdvcnt && ++ctx.cmd_ctx.cmdvecidx != ctx.cmd_ctx.cmdvcnt) {
        const struct iovec *iovec = ctx.cmd_ctx.cmdvec + ctx.cmd_ctx.cmdvecidx;
        HAL_UART_Transmit_IT(huart, (uint8_t *) iovec->iov_base, iovec->iov_len);
    } else {
        ctx.cmd_ctx.cmd_writing = 0;
    }
}

static inline HAL_StatusTypeDef esp8266_uart_write_it(struct esp8266_uart *uart, uint8_t *data, uint16_t size)
{
    return HAL_UART_Transmit_IT(uart->huart, data, size);
}





static inline int esp8266_wifi_connected(struct esp8266_ctx *ctx)
{
    return (ctx->status & (INIT_JOIN_AP_CFG_DONE | AP_JOINED | GOT_IP)) == (INIT_JOIN_AP_CFG_DONE | AP_JOINED | GOT_IP);
}

static HAL_StatusTypeDef esp8266_cmd_internal(struct esp8266_ctx *ctx, const char *cmdstr, const struct iovec *cmdvec, size_t cmdcnt, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *udata, uintptr_t udata2, ...);

static inline void esp8266_delay(struct esp8266_ctx *ctx, uint32_t timeout, cmd_done_cb_t delay_done_cb, void *udata, uintptr_t udata2)
{
    esp8266_cmd_internal(ctx, NULL, 0, 0, timeout, delay_done_cb, udata, udata2, "/* no need response, should until timeout */", NULL);
}

static inline int esp8266_cmd_is_end(struct esp8266_cmd_ctx *ctx)
{
    return (ctx->cmd[0] && !ctx->cmd_len) || (!ctx->cmd[0] && !ctx->cmdvcnt) || (ctx->tab_idx >= 0 && ctx->wait_rsp_expect_tab[ctx->tab_idx][ctx->expect_idx] == NULL);
}

/*******************************************************************************/

#define CLI_CHANNEL_TAB_LEN 8

struct {
    int connected;
} cli_channel_tab[CLI_CHANNEL_TAB_LEN];

static int parse_channel(const char *line, const char *token_ptr)
{
    char *endptr;
    if (token_ptr-- == line) {
        LOGe("%s: parse channel error: not contain channel field", line);
        return -1;
    }
    while (token_ptr != line) {
        if (isdigit(*token_ptr)) {
            --token_ptr;
        } else {
            break;
        }
    }
    ++token_ptr;

    unsigned long channel = strtoul(token_ptr, &endptr, 10);
    if (endptr == line) {
        LOGe("%s: parse channel error", line);
        return -1;
    }
    if (channel >= CLI_CHANNEL_TAB_LEN) {
        LOGe("%s: cannot process this channel", line);
    }
    return (int) channel;
}



static void ev_wifi_joined(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    ctx->status |= AP_JOINED;
    LOGi("wifi joined to %s", ctx->cfg.ssid);
}

static void ev_wifi_disconnect(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    ctx->status &= ~(INIT_JOIN_AP_CFG_DONE | AP_JOINED | GOT_IP | JUST_REDIAL_TCP);
    LOGi("wifi disconnected %s", ctx->cfg.ssid);
}

static HAL_StatusTypeDef esp8266_connect_to_server(struct esp8266_ctx *ctx);
static void ev_wifi_got_ip(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    ctx->status |= GOT_IP;
    if (esp8266_wifi_connected(ctx)) {
        esp8266_connect_to_server(ctx);
    }
}

static void ev_tcp_connected(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    // 模块作为 client 时，连接服务器成功
    ctx->status |= TCP_CONNECTED;
    ctx->status &= ~JUST_REDIAL_TCP;
    if (esp8266_tcp_connected()) {
        esp8266_on_tcp_connected(ctx);
    }
}

static void esp8266_init_delay_reconnect_to_server_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2);
static void ev_tcp_closed(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    // 模块作为 client 时，连接断开
    int conn_is_lost = 0;
    if (ctx->status & TCP_CONNECTED) {
        conn_is_lost = 1;
    }
    ctx->status &= ~(TCP_DIAL_DONE | TCP_CONNECTED);

    if (conn_is_lost) {
        LOGw("TCP connection lost");
        if (esp8266_wifi_connected(ctx)) {
            ctx->status |= JUST_REDIAL_TCP;
        }
    }
}

static void ev_connected(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    // 模块作为 server 时，收到连接
    int channel = parse_channel(line, token_ptr);
    if (channel >= 0 && channel < CLI_CHANNEL_TAB_LEN) {
        cli_channel_tab[channel].connected = 1;
    }
}

static void ev_closed(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    // 模块作为 server 时，连接断开
    int channel = parse_channel(line, token_ptr);
    if (channel >= 0 && channel < CLI_CHANNEL_TAB_LEN) {
        cli_channel_tab[channel].connected = 0;
    }
}

static void ev_ipd(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr)
{
    char *endptr;
    unsigned long nbytes;
    int channel = -1;

    // 提取数据
    // +IPD,[ch,]n:xxx
    nbytes = strtoul(token_ptr + 5, &endptr, 10);
    if (*endptr == ',') {
        // 包含通道号
        channel = nbytes;
        nbytes = strtoul(endptr + 1, &endptr, 10);
    }
    ++endptr;

    depkt_and_handle(channel, endptr, nbytes);
}



static const struct {
    const char *token;
    const int token_should_full_match;
    void (*handler)(struct esp8266_ctx *ctx, const char *line, size_t len, const char *token_ptr);
} ev_tab[] = {
        {"WIFI CONNECTED\r\n",  1, ev_wifi_joined},     // WiFi 连接到热点成功
        {"WIFI DISCONNECT\r\n", 1, ev_wifi_disconnect}, // WiFi 断开
        {"WIFI GOT IP\r\n",     1, ev_wifi_got_ip},     // WiFi 连接成功后，获取到了 IP 地址
        {"CONNECT\r\n",         1, ev_tcp_connected},   // 模块作为 client 时，连接服务器成功
        {"CLOSED\r\n",          1, ev_tcp_closed},      // 模块作为 client 时，连接断开
        {",CONNECT",            0, ev_connected},       // 模块作为 server 时，收到连接
        {",CLOSED",             0, ev_closed},          // 模块作为 server 时，连接断开
        {"+IPD,",               0, ev_ipd},             // 收到数据
};
/*******************************************************************************/







static inline int esp8266_cmd_is_timeout(struct esp8266_cmd_ctx *ctx)
{
    return !ctx->cmd_writing && HAL_GetTick() - ctx->tickstart > ctx->timeout;
}

static inline void esp8266_cmd_force_end(struct esp8266_cmd_ctx *ctx)
{
    ctx->cmd_len = 0;
    ctx->cmdvcnt = 0;
}

static void esp8266_prepare_init_cmd_ctx(struct esp8266_cmd_ctx *ctx, const char *cmdstr, const struct iovec *cmdvec, size_t cmdcnt, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *cmd_done_cb_udata, uintptr_t cmd_done_cb_udata2)
{
    char *cmdbuf = (char *) ctx->cmd;
    if (cmdvec && cmdcnt) {
        ctx->cmd_len = 0;

        ctx->cmdvec = cmdvec;
        ctx->cmdvcnt = cmdcnt;
    } else {
        ctx->cmdvcnt = 0;

        if (cmdstr == NULL || cmdstr[0] == '\0') {
            cmdbuf[0] = '\r';
            cmdbuf[1] = '\n';
            ctx->cmd_len = 2;
        } else {
            size_t cmdlen = cmdcnt;
            if (!cmdlen) {
                cmdlen = strlen(cmdstr);
            }
            memcpy(cmdbuf, cmdstr, cmdlen);
            if (!cmdcnt && (cmdlen < 2 || cmdbuf[cmdlen - 1] != '\n' || cmdbuf[cmdlen - 2] != '\r')) {
                cmdbuf[cmdlen] = '\r';
                cmdbuf[cmdlen + 1] = '\n';
                ctx->cmd_len = cmdlen + 2;
            } else {
                ctx->cmd_len = cmdlen;
            }
        }
    }

    cmdbuf[ctx->cmd_len] = '\0';
    ctx->cmdvecidx = 0;

    ctx->rsp = NULL;
    ctx->rsp_len = 0;
    ctx->tickstart = HAL_GetTick();
    ctx->timeout = timeout;
    ctx->wait_rsp_expect_tab[0][0] = NULL;
    ctx->tab_idx = -1;
    ctx->expect_idx = 0;
    ctx->rspbuf[0] = '\0';
    ctx->rspbuf_write_idx = 0;

    ctx->cmd_done_cb = cmd_done_cb;
    ctx->udata = cmd_done_cb_udata;
    ctx->udata2 = cmd_done_cb_udata2;
}

static void esp8266_prepare_single_case_cmd_ctx(struct esp8266_cmd_ctx *ctx, const char *cmdstr, const struct iovec *cmdvec, size_t cmdcnt, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *cmd_done_cb_udata, uintptr_t cmd_done_cb_udata2, va_list va)
{
    const char *expect;
    int expect_idx;

    esp8266_prepare_init_cmd_ctx(ctx, cmdstr, cmdvec, cmdcnt, timeout, cmd_done_cb, cmd_done_cb_udata, cmd_done_cb_udata2);

    expect_idx = 0;
    do {
        expect = va_arg(va, const char *);
        ctx->wait_rsp_expect_tab[0][expect_idx++] = expect;
    } while (expect);

    ctx->wait_rsp_expect_tab[1][0] = NULL;
}

static void esp8266_prepare_multi_case_cmd_ctx(struct esp8266_cmd_ctx *ctx, const char *cmdstr, const struct iovec *cmdvec, size_t cmdcnt, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *cmd_done_cb_udata, uintptr_t cmd_done_cb_udata2, va_list va)
{
    const char **tab;
    int tab_idx;
    int expect_idx;

    esp8266_prepare_init_cmd_ctx(ctx, cmdstr, cmdvec, cmdcnt, timeout, cmd_done_cb, cmd_done_cb_udata, cmd_done_cb_udata2);

    tab_idx = 0;
    do {
        tab = va_arg(va, const char **);
        if (tab) {
            expect_idx = 0;
            do {
                ctx->wait_rsp_expect_tab[tab_idx][expect_idx] = tab[expect_idx];
            } while (tab[expect_idx++]);
            ++tab_idx;
        } else {
            break;
        }
    } while (1);
    ctx->wait_rsp_expect_tab[tab_idx][0] = NULL;
}

static int esp8266_is_cmd_rsp(struct esp8266_cmd_ctx *ctx, const char *line, size_t line_len)
{
    int i;
    int found = 0;

    if (ctx->tab_idx < 0) {
        // 还未确定回响表中的具体索引
        for (i = 0; ctx->wait_rsp_expect_tab[i][0] != NULL; ++i) {
            if (strstr(line, ctx->wait_rsp_expect_tab[i][0])) {
                // 就是期望的回响字符串
                found = 1;
                ctx->tab_idx = i;
                ctx->expect_idx = 0;
                break;
            }
        }
    } else if (ctx->wait_rsp_expect_tab[ctx->tab_idx][ctx->expect_idx] == NULL) {
        // 命令已经完成
        return 0;
    } else if (line_len == 2 && line[0] == '\r' && line[1] == '\n') {
        // 空行
        if (ctx->wait_rsp_expect_tab[ctx->tab_idx][ctx->expect_idx][0] == '\0' ||
            (ctx->wait_rsp_expect_tab[ctx->tab_idx][ctx->expect_idx][0] == '\r' &&
             ctx->wait_rsp_expect_tab[ctx->tab_idx][ctx->expect_idx][1] == '\n' &&
             ctx->wait_rsp_expect_tab[ctx->tab_idx][ctx->expect_idx][2] == '\0')) {
            // 期望的回响就是空行
            found = 1;
        }
    } else if (strcmp(line, "busy p...\r\n") == 0) {
        return HAL_BUSY;
    } else if (strstr(line, ctx->wait_rsp_expect_tab[ctx->tab_idx][ctx->expect_idx])) {
        // 就是期望的回响字符串
        found = 1;
    }

    if (found) {
        memmove(ctx->rspbuf + ctx->rspbuf_write_idx, line, line_len + 1 /* 需要把 '\0' 复制进去 */);
        ctx->rspbuf_write_idx += line_len;
        // ctx->expect_idx 指向下一个期望的回响字符串
        ++ctx->expect_idx;
        if (ctx->rsp == NULL) {
            ctx->rsp = ctx->rspbuf;
        }
    }

    return found;
}

/**
 * @brief 丢弃接受到的数据
 */
static inline void esp8266_flush_recv(struct esp8266_uart *uart) {
    FIFO_RESET(uart->read_fifo);
}

#ifdef EN_ESP8266_CMD_BLOCKING
/**
 * @brief 读取一行 \r\n 结束的数据
 * @param buf 缓存
 * @param size 缓存大小，以及读到的实际字节数
 * @param timeout 超时时间
 * @return 一行数据
 */
static const char *esp8266_read_line(struct esp8266_uart *uart, char *buf, size_t *size, uint32_t tickstart, uint32_t timeout)
{
    size_t idx = 0;
    size_t buf_siz = *size;

    do {
        AGAIN:
        if (FIFO_LEN(&uart3_fifo)) {
            buf[idx] = FIFO_GET_BYTE(uart->read_fifo);
            if (buf[idx] == '\n' && idx > 0 && buf[idx - 1] == '\r') {
                ++idx;
                buf[idx] = '\0';
                *size = idx;
                return buf;
            }
            if (++idx == buf_siz - 1) {
                buf[idx] = '\0';
                *size = idx;
                return buf;
            }
            goto AGAIN; // 无数据时，才判断超时
        }
    } while (timeout == 0 || ((HAL_GetTick() - tickstart) < timeout));

    buf[idx] = '\0';
    *size = idx;
    return buf;
}
#endif



static HAL_StatusTypeDef esp8266_v_cmd(struct esp8266_ctx *ctx, const char *cmdstr, const struct iovec *cmdvec, size_t cmdcnt, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *cmd_done_cb_udata, uintptr_t cmd_done_cb_udata2,
        void (*cmd_ctx_prepare)(struct esp8266_cmd_ctx *ctx, const char *cmdstr, const struct iovec *cmdvec, size_t cmdcnt, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *cmd_done_cb_udata, uintptr_t cmd_done_cb_udata2, va_list va),
        va_list va)
{
    AT_CMD_DBG(" > %s\r\n", cmdstr ? cmdstr : "");

    if (!esp8266_cmd_is_end(&ctx->cmd_ctx)) {
        AT_CMD_DBG(" E BUSY (last cmd not end)\r\n");
        return HAL_BUSY;
    }

    cmd_ctx_prepare(&ctx->cmd_ctx, cmdstr, cmdvec, cmdcnt, timeout, cmd_done_cb, cmd_done_cb_udata, cmd_done_cb_udata2, va);

    if (cmdstr) {
        ctx->cmd_ctx.cmd_writing = 1;
        return esp8266_uart_write_it(&ctx->uart, (uint8_t *) ctx->cmd_ctx.cmd, (uint16_t) ctx->cmd_ctx.cmd_len);
    }

    if (cmdvec && cmdcnt) {
        ctx->cmd_ctx.cmd_writing = 1;
        const struct iovec *iovec = ctx->cmd_ctx.cmdvec + ctx->cmd_ctx.cmdvecidx;
        return esp8266_uart_write_it(&ctx->uart, (uint8_t *) iovec->iov_base, (uint16_t) iovec->iov_len);
    }

    ctx->cmd_ctx.cmd_writing = 0;

    // 不需要发送数据，可能是用于delay的假命令。
    return HAL_OK;
}

static HAL_StatusTypeDef esp8266_cmd_internal(struct esp8266_ctx *ctx, const char *cmdstr, const struct iovec *cmdvec, size_t cmdcnt, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *udata, uintptr_t udata2, ...)
{
    va_list va;
    HAL_StatusTypeDef status;
    va_start(va, udata2);
    status = esp8266_v_cmd(ctx, cmdstr, cmdvec, cmdcnt, timeout, cmd_done_cb, udata, udata2, esp8266_prepare_single_case_cmd_ctx, va);
    va_end(va);
    return status;
}

HAL_StatusTypeDef esp8266_cmd(struct esp8266_ctx *ctx, const char *atcmd, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *udata, uintptr_t udata2, ...)
{
    va_list va;
    HAL_StatusTypeDef status;
    va_start(va, udata2);
    status = esp8266_v_cmd(ctx, atcmd, NULL, 0, timeout, cmd_done_cb, udata, udata2, esp8266_prepare_single_case_cmd_ctx, va);
    va_end(va);
    return status;
}

HAL_StatusTypeDef esp8266_cmd_multi_case_rsp(struct esp8266_ctx *ctx, const char *atcmd, uint32_t timeout, cmd_done_cb_t cmd_done_cb, void *udata, uintptr_t udata2, ...)
{
    va_list va;
    HAL_StatusTypeDef status;
    va_start(va, udata2);
    status = esp8266_v_cmd(ctx, atcmd, NULL, 0, timeout, cmd_done_cb, udata, udata2, esp8266_prepare_multi_case_cmd_ctx, va);
    va_end(va);
    return status;
}

static int esp8266_is_ev(struct esp8266_ctx *ctx, const char *line, size_t line_len)
{
    int i;
    const char *token_ptr;

    for (i = 0; i < ARR_SIZE(ev_tab); ++i) {
        if (ev_tab[i].token_should_full_match) {
            token_ptr = NULL;
            if (strcmp(line, ev_tab[i].token) == 0) {
                token_ptr = line;
            }
        } else {
            token_ptr = strstr(line, ev_tab[i].token);
        }
        if (token_ptr) {
            ev_tab[i].handler(ctx, line, line_len, token_ptr);
            return 1;
        }
    }

    return 0;
}

/**
 * @brief
 * @param ctx
 * @param line
 * @param line_len
 * @return -1 未知数据，0 事件，1 命令回响，2 不是明确的命令回响行
 */
static int esp8266_handle_line(struct esp8266_ctx *ctx, const char *line, size_t line_len)
{
    if (esp8266_cmd_is_end(&ctx->cmd_ctx)) {
        if (esp8266_is_ev(ctx, line, line_len)) {
            return 0;
        }
        return -1;
    } else if (esp8266_is_cmd_rsp(&ctx->cmd_ctx, line, line_len)) {
        return  1;
    } else if (esp8266_is_ev(ctx, line, line_len)) {
        return 0;
    } else {
        // 认为是命令回响的一部分
        memmove(ctx->cmd_ctx.rspbuf + ctx->cmd_ctx.rspbuf_write_idx, line, line_len + 1 /* 需要把 '\0' 复制进去 */);
        ctx->cmd_ctx.rspbuf_write_idx += line_len;
        if (ctx->cmd_ctx.rsp == NULL) {
            ctx->cmd_ctx.rsp = ctx->cmd_ctx.rspbuf;
        }
        return 2;
    }
}

#ifdef EN_ESP8266_CMD_BLOCKING
HAL_StatusTypeDef esp8266_cmd_blocking(struct esp8266_ctx *ctx, const char *atcmd, const char **rsp_ptr, uint32_t timeout, ...)
{

    va_list va;
    HAL_StatusTypeDef status;
    const char *line;
    size_t line_len;

    va_start(va, timeout);
    status = esp8266_v_cmd(ctx, atcmd, NULL, strlen(atcmd), timeout, NULL, NULL, 0, esp8266_prepare_single_case_cmd_ctx, va);
    va_end(va);

    if (status != HAL_OK) {
        return status;
    }

    while (ctx->cmd_ctx.cmd_writing)
        ;

    do {
        line_len = sizeof(ctx->buf);
        line = esp8266_read_line(&ctx->uart, ctx->buf, &line_len, ctx->cmd_ctx.tickstart, ctx->cmd_ctx.timeout);
        if (line_len == 0) {
            // 超时了
            AT_CMD_DBG(" E timeout\r\n");
            return HAL_TIMEOUT;
        }

        AT_CMD_DBG(" <  %s", line);

        if (esp8266_handle_line(ctx, line, line_len) == 1) {
            if (esp8266_cmd_is_end(&ctx->cmd_ctx)) {
                if (rsp_ptr) {
                    *rsp_ptr = ctx->cmd_ctx.rsp;
                }
                return HAL_OK;
            }
        }
    } while (1);
}
#endif





/****************** 发送数据到网络 *********************************************************/

static void esp8266_net_send_stage2_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    ctx->status &= ~IN_CIPSEND;
    if (cmd_ctx->cmdvec == &ctx->iovec) {
        ctx->status &= ~IOVEC_BUSY;
    }
    if (status != HAL_OK) {
        LOGw("AT+CIPSEND write data: %s", HAL_StatusTypeDef_str(status));
    } else {
        LOGi("AT+CIPSEND write data: %s", HAL_StatusTypeDef_str(status));
    }
    if (ctx->net_send_done_cb) {
        ctx->net_send_done_cb(status, ctx->net_send_done_cb_udata);
    }
}

static void esp8266_net_send_stage1_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    ctx->status &= ~IN_CIPSEND_STAGE_1;

    if (status == HAL_OK && strstr(cmd_ctx->rsp, "OK\r\n> ")) {
        // 写数据
        struct iovec *cmdvec = udata;
        size_t cmdvcnt = udata2;
        int i;
        size_t databyes = 0;
        for (i = 0; i < cmdvcnt; ++i) {
            databyes += cmdvec[i].iov_len;
        }
        esp8266_cmd_internal(ctx, NULL, cmdvec, cmdvcnt, 10000 + databyes, esp8266_net_send_stage2_done_cb, NULL, 0, "SEND OK", NULL);
        return;
    }

    ctx->status &= ~IN_CIPSEND;
    if (cmd_ctx->cmdvec == &ctx->iovec) {
        ctx->status &= ~IOVEC_BUSY;
    }

    if (status != HAL_OK) {
        LOGw("[%s]: %s", cmd_ctx->cmd, HAL_StatusTypeDef_str(status));
    } else {
        status = HAL_ERROR;
        LOGw("[%s]: ERROR", cmd_ctx->cmd);
    }

    if (ctx->net_send_done_cb) {
        ctx->net_send_done_cb(status, ctx->net_send_done_cb_udata);
    }
}

static HAL_StatusTypeDef esp8266_net_send_internal(struct esp8266_ctx *ctx, int ch, const struct iovec *iovec, int iovcnt, net_send_done_cb_t done_cb, void *udata)
{
    HAL_StatusTypeDef status;
    char buf[32];
    int i;
    unsigned int dat_siz = 0;

    if (ctx->status & IN_CIPSEND) {
        LOGe(" E BUSY: has net data sending");
        return HAL_BUSY;
    }

    ctx->status |= IN_CIPSEND | IN_CIPSEND_STAGE_1;

    // 计算总共需要发送的字节数
    for (i = 0; i < iovcnt; ++i) {
        dat_siz += iovec[i].iov_len;
    }

    if (ch < 0) {
        snprintf(buf, sizeof(buf), "AT+CIPSEND=%u", dat_siz);
    } else {
        snprintf(buf, sizeof(buf), "AT+CIPSEND=%d,%u", ch, dat_siz);
    }

    const char *ok_rsp[] = { "OK", "> ", NULL };
    const char *err_rsp[] = { "ERROR", NULL };
    status = esp8266_cmd_multi_case_rsp(ctx, buf, 10000, esp8266_net_send_stage1_done_cb, (void *) iovec, iovcnt, ok_rsp, err_rsp, NULL);
    if (status == HAL_OK) {
        ctx->net_send_done_cb = done_cb;
        ctx->net_send_done_cb_udata = udata;
    } else {
        ctx->status &= ~(IN_CIPSEND | IN_CIPSEND_STAGE_1);
    }
    return status;
}

/****************** 发送数据到网络 end *****************************************************/



/****************** ESP8266 连接服务器 *****************************************************/

static void esp8266_init_delay_reconnect_to_server_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    if (esp8266_wifi_connected(ctx)) {
        esp8266_connect_to_server(ctx);
    }
}

static void esp8266_net_dial_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    ctx->status |= TCP_DIAL_DONE;

    if (status != HAL_OK) {
        LOGw("dial to %s:%d %s", ctx->cfg.server_host, ctx->cfg.server_port, HAL_StatusTypeDef_str(status));
        return;
    }

    if (strstr(cmd_ctx->rsp, "ALREDY CONNECT")) {
        ctx->status |= TCP_CONNECTED;
        LOGi("dial to %s:%d ALREDY CONNECT", ctx->cfg.server_host, ctx->cfg.server_port);
        if (esp8266_tcp_connected()) {
            esp8266_on_tcp_connected(ctx);
        }
        return;
    }

    if (strstr(cmd_ctx->rsp, "OK")) {
        LOGi("dial to %s:%d OK", ctx->cfg.server_host, ctx->cfg.server_port);
        if (esp8266_tcp_connected()) {
            esp8266_on_tcp_connected(ctx);
        }
        return;
    }

    if (strstr(cmd_ctx->rsp, "ERROR")) {
        LOGw("dial to %s:%d ERROR", ctx->cfg.server_host, ctx->cfg.server_port);
    } else {
        LOGw("dial to %s:%d FAIL", ctx->cfg.server_host, ctx->cfg.server_port);
    }

    // 延迟5秒，重新连接服务器
    esp8266_delay(ctx, 5000, esp8266_init_delay_reconnect_to_server_cb, NULL, 0);
}

static HAL_StatusTypeDef esp8266_net_dial(struct esp8266_ctx *ctx, int CIPMUX)
{
    char atcmd[256];

    if (CIPMUX == 0) {
        // 单链路模式
        snprintf(atcmd, sizeof(atcmd), "AT+CIPSTART=\"TCP\",\"%s\",%d", ctx->cfg.server_host, ctx->cfg.server_port);
    } else {
        // 多链路模式
        snprintf(atcmd, sizeof(atcmd), "AT+CIPSTART=0,\"TCP\",\"%s\",%d", ctx->cfg.server_host, ctx->cfg.server_port);
    }
    // 连接
    const char *ok[] = { "OK", NULL };
    const char *err[] = { "ERROR", NULL };
    const char *alredy_conn[] = { "ALREDY CONNECT", NULL };
    return esp8266_cmd_multi_case_rsp(ctx, atcmd, 60000, esp8266_net_dial_done_cb, NULL, CIPMUX, ok, err, alredy_conn, NULL);
}

static void esp8266_get_CIPMUX_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    char *str;
    unsigned long mode;
    const char cipmux_rsp_head[] = "+CIPMUX:";

    if (status != HAL_OK) {
        LOGe("AT+CIPMUX? : %s", HAL_StatusTypeDef_str(status));
        return;
    }

    str = strstr(cmd_ctx->rsp, cipmux_rsp_head);
    if (!str) {
        LOGe("AT+CIPMUX? response parse error");
        return;
    }
    str += sizeof(cipmux_rsp_head) - 1;
    mode = strtoul(str, NULL, 10);

    esp8266_net_dial(ctx, mode);
}

static HAL_StatusTypeDef esp8266_get_CIPMUX(struct esp8266_ctx *ctx)
{
    return esp8266_cmd(ctx, "AT+CIPMUX?", 200, esp8266_get_CIPMUX_done_cb, NULL, 0, "+CIPMUX:", "", "OK", NULL);
}

#if 0
static void atk_8266_log_sta_ip_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    char *str;
    const char *ip;
    const char cipap_rsp_head[] = "+CIPSTA:ip:";

    if (status != HAL_OK) {
        LOGe("AT+CIPSTA? : %s", HAL_StatusTypeDef_str(status));
        return;
    }

    str = strstr(cmd_ctx->rsp, cipap_rsp_head);
    if (str) {
        // +CIPSTA:ip:"172.20.10.3"\r\n...
        str += sizeof(cipap_rsp_head) - 1;
        // "172.20.10.3"\r\n...
        ip = str + 1;
        // 172.20.10.3"\r\n...
        str = strstr(str, "\"\r\n");
        if (str) {
            *str = '\0';
            LOGi("got ip: %s", ip);
        }
    }

    esp8266_get_CIPMUX(ctx);
}

static HAL_StatusTypeDef atk_8266_log_sta_ip(struct esp8266_ctx *ctx)
{
    return esp8266_cmd(ctx, "AT+CIPSTA?", 200, atk_8266_log_sta_ip_done_cb, NULL, 0, "+CIPSTA:ip:", "", "OK", NULL);
}
#endif

static HAL_StatusTypeDef esp8266_connect_to_server(struct esp8266_ctx *ctx)
{
#if 0//def USE_LOG
    return atk_8266_log_sta_ip(ctx);
#else
    // 连接到服务器
    return esp8266_get_CIPMUX(ctx);
#endif
}

/****************** ESP8266 连接服务器 end *************************************************/



/****************** ESP8266 初始化 加入 AP *************************************************/


static void safe_strcat(char *dst, size_t size, const char *src)
{
    while (size && *dst) {
        ++dst;
        --size;
    }
    if (size == 0) {
        return;
    }
    while (size && *src) {
        *dst++ = *src++;
        --size;
    }
    if (size == 0) {
        --dst;
    }
    *dst = '\0';
}

static void safe_esc_strcat(char *dst, size_t size, const char *src)
{
    while (size && *dst) {
        ++dst;
        --size;
    }
    if (size == 0) {
        return;
    }
    while (size && *src) {
        char ch = *src++;
        if (ch == ',' || ch == '"' || ch == '\\') {
            *dst++ = '\\';
            if (!--size) {
                break;
            }
        }
        *dst++ = ch;
        --size;
    }
    if (size == 0) {
        --dst;
    }
    *dst = '\0';
}

static inline HAL_StatusTypeDef esp8266_init_AT_check(struct esp8266_ctx *ctx);
static inline HAL_StatusTypeDef esp8266_init_check_joined_ap(struct esp8266_ctx *ctx);
static HAL_StatusTypeDef esp8266_init_join_ap(struct esp8266_ctx *ctx);

static void esp8266_init_delay_rejoin_ap_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    esp8266_init_join_ap(ctx);
}

static void esp8266_init_join_ap_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    if (status != HAL_OK) {
        LOGe("ESP8266 join to \"%s\" : %s", ctx->cfg.ssid, HAL_StatusTypeDef_str(status));
    } else if (strstr(cmd_ctx->rsp, "OK")) {
        ctx->status |= INIT_JOIN_AP_CFG_DONE;
        if (esp8266_wifi_connected(ctx)) {
            esp8266_connect_to_server(ctx);
        }
        return;
    } else {
        LOGe("ESP8266 join to \"%s\" FAIL", ctx->cfg.ssid);
    }

    // 延时5秒，再次尝试加入Wi-Fi热点
    esp8266_delay(ctx, 5000, esp8266_init_delay_rejoin_ap_cb, NULL, 0);
}


static HAL_StatusTypeDef esp8266_init_join_ap(struct esp8266_ctx *ctx)
{
    char atcmd[256];
    const char *result_ok[] = {"OK", NULL};
    const char *result_fail[] = {"FAIL", NULL};

    strncpy(atcmd, "AT+CWJAP=", sizeof(atcmd));
    safe_strcat(atcmd, sizeof(atcmd), "\"");
    safe_esc_strcat(atcmd, sizeof(atcmd), ctx->cfg.ssid);
    safe_strcat(atcmd, sizeof(atcmd), "\",\"");
    safe_esc_strcat(atcmd, sizeof(atcmd), ctx->cfg.psk);
    safe_strcat(atcmd, sizeof(atcmd), "\"");
    return esp8266_cmd_multi_case_rsp(ctx, atcmd, 30000, esp8266_init_join_ap_done_cb, NULL, 0, result_ok, result_fail, NULL);
}

static void esp8266_init_check_joined_ap_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    const char *str;
    const char cwjap_rsp_head[] = "+CWJAP:";
    const char cwjap_rsp_no_ap[] = "No AP";

    if (status != HAL_OK) {
        // AT+CWJAP? 失败，重试
        esp8266_init_check_joined_ap(ctx);
        return;
    }

    // AT+CWJAP? 成功，提取已配置的 AP 信息
    str = strstr(cmd_ctx->rsp, cwjap_rsp_head);
    if (!str) {
        str = strstr(cmd_ctx->rsp, cwjap_rsp_no_ap);
        if (!str) {
            // 未得到返回？某个地方出错了？
            // 从头再来
            esp8266_flush_recv(&ctx->uart);
            esp8266_init_AT_check(ctx);
            return;
        }
    } else {
        // +CWJAP:"xxxx","xxx",...
        str += sizeof(cwjap_rsp_head);
        if (strstr(str, ctx->cfg.ssid) == str) {
            // 就是连接的这个 SSID
            ctx->status |= INIT_JOIN_AP_CFG_DONE;
            if (esp8266_wifi_connected(ctx)) {
                esp8266_connect_to_server(ctx);
            } else {
                // 等待连接成功，并且DHCP成功。
            }
            return;
        }
    }

    // 需要配置
    esp8266_init_join_ap(ctx);
}

static inline HAL_StatusTypeDef esp8266_init_check_joined_ap(struct esp8266_ctx *ctx)
{
    const char *cwap_tab[] = { "+CWJAP:", "OK", NULL };
    const char *noap_tab[] = { "No AP", "OK", NULL };
    return esp8266_cmd_multi_case_rsp(ctx, "AT+CWJAP?", 200, esp8266_init_check_joined_ap_done_cb, NULL, 0, cwap_tab, noap_tab, NULL);
}

/****************** ESP8266 初始化 加入 AP end *********************************************/



/****************** ESP8266 初始化 重启模块 *************************************************/

static inline HAL_StatusTypeDef esp8266_init_reset_module(struct esp8266_ctx *ctx);

static void esp8266_init_reset_module_sleep_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    // 重启命令（AT+RST）后的延时成功，进行下一步：测试 AT 命令是否可用
    esp8266_init_AT_check(ctx);
}

static void esp8266_init_reset_module_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    if (status != HAL_OK) {
        // AT+RST 超时?，重试
        esp8266_init_reset_module(ctx);
    } else {
        // AT+RST 成功，下一步：sleep/延时
        esp8266_delay(ctx, 1000, esp8266_init_reset_module_sleep_done_cb, NULL, 0);
    }
}

static inline HAL_StatusTypeDef esp8266_init_reset_module(struct esp8266_ctx *ctx)
{
    return esp8266_cmd(ctx, "AT+RST", 200, esp8266_init_reset_module_done_cb, NULL, 0, "OK", NULL);
}

/****************** ESP8266 初始化 重启模块 end *********************************************/



/****************** ESP8266 初始化 切换 Wi-Fi 模式 ******************************************/

typedef enum {
    WIFI_MODE_STA = 1,
    WIFI_MODE_AP = 2,
    WIFI_MODE_AP_STA = 3,
} WiFi_MODE;

static inline HAL_StatusTypeDef esp8266_init_check_wifi_mode(struct esp8266_ctx *ctx, WiFi_MODE expect_mode);

static void esp8266_init_wifi_mode_switch_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    if (status != HAL_OK) {
        // AT+CWMODE= 命令超时，从 检查模式 开始重试
        esp8266_init_check_wifi_mode(ctx, (WiFi_MODE) udata2);
    } else {
        // AT+CWMODE= 命令成功，进行下一步：重启模块
        esp8266_init_reset_module(ctx);
    }
}

static HAL_StatusTypeDef esp8266_init_wifi_mode_switch_to(struct esp8266_ctx *ctx, WiFi_MODE to_mode)
{
    char atcwmode[16];
    snprintf(atcwmode, sizeof(atcwmode), "AT+CWMODE=%d", to_mode);
    return esp8266_cmd(ctx, atcwmode, 200, esp8266_init_wifi_mode_switch_done_cb, NULL, to_mode, "OK", NULL);
}

static inline HAL_StatusTypeDef esp8266_init_check_wifi_mode(struct esp8266_ctx *ctx, WiFi_MODE expect_mode);

static void esp8266_init_check_wifi_mode_done_db(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    const char *str;
    unsigned long mode;
    unsigned long expect_mode = udata2;

    if (status != HAL_OK) {
        // AT+CWMODE? 命令超时，重试
        esp8266_init_check_wifi_mode(ctx, (WiFi_MODE) expect_mode);
    } else {
        // AT+CWMODE? 成功，检查提取 Wi-Fi 工作模式信息
        const char cwmode_rsp_head[] = "+CWMODE:";
        str = strstr(cmd_ctx->rspbuf, cwmode_rsp_head);
        if (!str) {
            // 返回数据错误，重试
            esp8266_flush_recv(&ctx->uart);
            esp8266_init_check_wifi_mode(ctx, (WiFi_MODE) expect_mode);
            return;
        }
        str += sizeof(cwmode_rsp_head) - 1;
        mode = strtoul(str, NULL, 10);
        if (mode == expect_mode) {
            // 就是在期望的模式，进行下一步：连接 WiFi 路由器
            esp8266_init_join_ap(ctx);
        } else {
            // 需要切换
            esp8266_init_wifi_mode_switch_to(ctx, (WiFi_MODE) expect_mode);
        }
    }
}

static inline HAL_StatusTypeDef esp8266_init_check_wifi_mode(struct esp8266_ctx *ctx, WiFi_MODE expect_mode)
{
    return esp8266_cmd(ctx, "AT+CWMODE?", 200, esp8266_init_check_wifi_mode_done_db, NULL, expect_mode, "+CWMODE:", "", "OK", NULL);
}

/****************** ESP8266 初始化 切换 Wi-Fi 模式 end **************************************/



/****************** ESP8266 初始化 设置串口 *************************************************/

static void esp8266_init_set_uart_baudrate_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    if (status != HAL_OK) {
        // 重新开始
        esp8266_init_AT_check(ctx);
    } else {
        // 改变STM32的波特率
        ctx->uart.huart->Init.BaudRate = udata2;
        status = HAL_UART_Init(&huart3);
        if (status != HAL_OK) {
            LOGe("HAL_UART_Init error %s", HAL_StatusTypeDef_str(status));
        } else {
            // 进行下一步：Wi-Fi 设置到 Station 模式
            esp8266_init_check_wifi_mode(ctx, WIFI_MODE_STA);
        }
    }
}

static inline HAL_StatusTypeDef esp8266_init_set_uart_baudrate(struct esp8266_ctx *ctx, int baudRate)
{
    char buf[32];
    snprintf(buf, sizeof(buf), "AT+UART=%d,8,1,0,0", baudRate);
    return esp8266_cmd(ctx, buf, 300, esp8266_init_set_uart_baudrate_done_cb, NULL, baudRate, "OK", NULL);
}

/****************** ESP8266 初始化 设置串口 end *********************************************/



/****************** ESP8266 初始化 关闭回显 *************************************************/

static inline HAL_StatusTypeDef esp8266_init_ATE0(struct esp8266_ctx *ctx);

static void esp8266_init_ATE0_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    if (status != HAL_OK) {
        // ATE0 命令超时?，重试
        esp8266_init_ATE0(ctx);
    } else {
        if (ctx->uart.huart->Init.BaudRate != 38400) {
            // 配置模块波特率
            esp8266_init_set_uart_baudrate(ctx, 38400);
        } else {
            // ATE0 命令成功，进行下一步：Wi-Fi 设置到 Station 模式
            esp8266_init_check_wifi_mode(ctx, WIFI_MODE_STA);
        }
    }
}

static inline HAL_StatusTypeDef esp8266_init_ATE0(struct esp8266_ctx *ctx)
{
    esp8266_flush_recv(&ctx->uart);
    return esp8266_cmd(ctx, "ATE0", 200, esp8266_init_ATE0_done_cb, NULL, 0, "OK", NULL);
}

/****************** ESP8266 初始化 关闭回显 end *********************************************/



/****************** ESP8266 初始化 退出透传模式 **********************************************/

static inline HAL_StatusTypeDef esp8266_init_AT_check(struct esp8266_ctx *ctx);

static void esp8266_init_quit_trans_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    esp8266_init_AT_check(ctx);
}

static inline HAL_StatusTypeDef esp8266_init_quit_trans(struct esp8266_ctx *ctx)
{
    return esp8266_cmd(ctx, "AT+CIPMODE=0", 200, esp8266_init_quit_trans_done_cb, NULL, 0, "OK", NULL);
}

static void esp8266_init_end_trans_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    ctx->status &= ~IOVEC_BUSY;
    esp8266_flush_recv(&ctx->uart);
    esp8266_init_quit_trans(ctx);
}

static inline HAL_StatusTypeDef esp8266_init_end_trans(struct esp8266_ctx *ctx)
{
    return esp8266_cmd_internal(ctx, "+++", 0, 3, 600, esp8266_init_end_trans_done_cb, NULL, 0, "/* no need response, should until timeout */", NULL);
}

/****************** ESP8266 初始化 退出透传模式 end ******************************************/



/****************** ESP8266 初始化 AT 命令测试 **********************************************/

static void esp8266_init_AT_check_done_cb(struct esp8266_ctx *ctx, struct esp8266_cmd_ctx *cmd_ctx, HAL_StatusTypeDef status, void *udata, uintptr_t udata2)
{
    if (status != HAL_OK) {
        LOGe("AT check %d: %s", ctx->AT_check_failure_times, HAL_StatusTypeDef_str(status));
        if (++ctx->AT_check_failure_times > 3) {
            if (ctx->AT_check_failure_times > 6) {
                ctx->AT_check_failure_times = 0;
                ctx->uart.huart->Init.BaudRate = 38400;
            } else {
                ctx->uart.huart->Init.BaudRate = 115200;
            }
            status = HAL_UART_Init(&huart3);
            if (status != HAL_OK) {
                LOGe("HAL_UART_Init error %s", HAL_StatusTypeDef_str(status));
            }
        }
        // AT 命令超时，尝试退出透传模式
        esp8266_init_end_trans(ctx);
    } else {
        // AT 命令成功，进行下一步：关闭回显
        esp8266_init_ATE0(ctx);
    }
}

static inline HAL_StatusTypeDef esp8266_init_AT_check(struct esp8266_ctx *ctx)
{
    esp8266_flush_recv(&ctx->uart);
    return esp8266_cmd(ctx, "AT", 500, esp8266_init_AT_check_done_cb, NULL, 0, "OK", NULL);
}

/****************** ESP8266 初始化 AT 命令测试 end ******************************************/


HAL_StatusTypeDef esp8266_init(const char *ssid, const char *psk, const char *server_host, int server_port)
{
    ctx.status = 0;
    ctx.cfg.ssid = ssid;
    ctx.cfg.psk = psk;
    ctx.cfg.server_host = server_host;
    ctx.cfg.server_port = server_port;
    ctx.uart.huart = &huart3;
    ctx.uart.read_fifo = &uart3_fifo;
    ctx.ipd_n = -2;

    // 检查 Wi-Fi 模块是否在线
    // 从检查Wi-Fi模块是否在线开始，是链式结构，在AT命令的回调中进行下一步

    return esp8266_init_AT_check(&ctx);
}

int esp8266_tcp_connected(void)
{
    unsigned int mask = (INIT_JOIN_AP_CFG_DONE | AP_JOINED | TCP_DIAL_DONE | TCP_CONNECTED);
    return (ctx.status & mask) == mask;
}

HAL_StatusTypeDef esp8266_net_send(const void *data, size_t len, net_send_done_cb_t done_cb, void *udata)
{
    HAL_StatusTypeDef status;
    if (esp8266_tcp_connected()) {
        if (ctx.status & IOVEC_BUSY) {
            return HAL_BUSY;
        }
        ctx.status |= IOVEC_BUSY;
        ctx.iovec.iov_base = data;
        ctx.iovec.iov_len = len;
        status = esp8266_net_send_internal(&ctx, -1, &ctx.iovec, 1, done_cb, udata);
        if (status != HAL_OK) {
            ctx.status &= ~IOVEC_BUSY;
        }
        return status;
    }
    return HAL_BUSY;
}

HAL_StatusTypeDef esp8266_net_sendv(const struct iovec *iov, int iovcnt, net_send_done_cb_t done_cb, void *udata)
{
    if (esp8266_tcp_connected()) {
        return esp8266_net_send_internal(&ctx, -1, iov, iovcnt, done_cb, udata);
    }
    return HAL_BUSY;
}

static int esp8266_is_line_complete(struct esp8266_ctx *ctx, char ch)
{
    if (ctx->ipd_n >= -1) {
        // 正在处于接收 +IPD,n:xxx 数据的过程中
        if (ctx->ipd_n != -1) {
            --ctx->ipd_n;
        } else {
            switch (ch) {
                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                case ',':
                    return 0;
                default:
                    // 不是网络数据的通知
                    ctx->ipd_n = -2;
                    return 0;
                case ':': {
                    // 解析 n
                    char *endptr;
                    unsigned long digit;
                    // 需要处理带通道号的情况 +IPD,ch,n:
                    buf[bufidx + 1] = '\0';
                    digit = strtoul(buf + 5, &endptr, 10);
                    if (*endptr == ',') {
                        // 第一个数字是通道号，逗号后面才是数据字节数
                        digit = strtoul(endptr + 1, &endptr, 10);
                        if (*endptr != ':') {
                            // 出错，可能不是网络数据
                            ctx->ipd_n = -2;
                            return 0;
                        }
                        ctx->ipd_n = digit;
                    } else if (*endptr != ':') {
                        // 出错，可能不是网络数据
                        ctx->ipd_n = -2;
                        return 0;
                    } else {
                        ctx->ipd_n = digit;
                    }
                }
                    break;
            }
            // 根据字节数延长定时器
            ctx->ipd_timeout = 50 + ctx->ipd_n;
        }

        if (ctx->ipd_n == 0) {
            // 数据接收完成
            ctx->ipd_n = -2;
            return 1;
        }

        return 0;
    }
    // +IPD,n:xxx 接收到网络数据，直接通过 n 来判断是否结束，没有回车换行
    if (ch == ',' && bufidx == 4 && strncmp(buf, "+IPD", 4) == 0) {
        ctx->ipd_n = -1;
        // 启动超时定时器
        ctx->ipd_timeout = 50;
        ctx->ipd_tickstart = HAL_GetTick();
        return 0;
    }

    // 正常的以回车换行结束的
    if (ch == '\n' && bufidx > 0 && buf[bufidx - 1] == '\r') {
        return 1;
    }

    // AT+CIPSEND 命令回响的 "> " 后面没有回车换行
    if (ch == ' ' && bufidx == 1 && buf[0] == '>' && (ctx->status & IN_CIPSEND_STAGE_1)) {
        return 1;
    }

    return 0;
}

static void esp8266_task_fun(void)
{
    while (FIFO_LEN(&uart3_fifo)) {
        // ATK-ESP8266 模块串口有数据可读

        buf[bufidx] = FIFO_GET_BYTE(&uart3_fifo); // 读取一个字节，放入待处理数据缓存里
        if (esp8266_is_line_complete(&ctx, buf[bufidx])) {
            // 已经是一个完整的行

            // 添加字符串结束符
            buf[++bufidx] = '\0';

            AT_CMD_DBG(" : %s", buf);

            if (esp8266_handle_line(&ctx, buf, bufidx) == 1) {
                if (esp8266_cmd_is_end(&ctx.cmd_ctx)) {
                    if (ctx.cmd_ctx.cmd_done_cb) {
                        ctx.cmd_ctx.cmd_done_cb(&ctx, &ctx.cmd_ctx, HAL_OK, ctx.cmd_ctx.udata, ctx.cmd_ctx.udata2);
                    }
                }
            }

            // 重置待处理数据缓存
            bufidx = 0;

        } else if (++bufidx == sizeof(buf) - 1) { // 留一个字符，存放字符串结束符 '\0'
            // 一行数据超出了缓冲区，很可能是错误的数据，我们丢弃一个字节。
            memmove(buf, buf + 1, --bufidx);
        }
    }

    if (ctx.ipd_n >= -1) {
        // 接收网络数据(+IPD,n:xxx)中
        if (ctx.ipd_timeout && HAL_GetTick() - ctx.ipd_tickstart > ctx.ipd_timeout) {
            // 超时
            // 我们丢弃这些数据
            bufidx = 0;
            ctx.ipd_n = -2;
            ctx.ipd_timeout = 0;
            LOGe("+IPD error");
        }
    } else if (!esp8266_cmd_is_end(&ctx.cmd_ctx) && esp8266_cmd_is_timeout(&ctx.cmd_ctx)) {
        esp8266_cmd_force_end(&ctx.cmd_ctx);
        if (ctx.cmd_ctx.cmd_done_cb) {
            ctx.cmd_ctx.cmd_done_cb(&ctx, &ctx.cmd_ctx, HAL_TIMEOUT, ctx.cmd_ctx.udata, ctx.cmd_ctx.udata2);
        }
    }

    if (ctx.status & JUST_REDIAL_TCP) {
        // 连接丢失，需要重连
        if (esp8266_cmd_is_end(&ctx.cmd_ctx)) {
            ctx.status &= ~JUST_REDIAL_TCP;
            // 延迟5秒，重连服务器
            esp8266_delay(&ctx, 5000, esp8266_init_delay_reconnect_to_server_cb, NULL, 0);
        }
    }
}

task_t esp8266_task = {
        TASK_FLAG_STAT_SLEEP,
        esp8266_task_fun
};
